﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Runtime.Serialization;
using System.IO;

namespace Pws.Clients.RestLibrary.Service
{
	#region Exception base classes for general exceptions
	/// <summary>
	/// Represents a generic exception which has been thrown during execution of an IPwsService method, when communicating
	/// with the PWS Rest web services.
	/// This exception is used as a base class for other exceptions, and is not particularly useful on its own.
	/// </summary>
	public class PwsServiceException : Exception
	{
		/// <summary>
		/// Generic error message when no specific message was trapped.
		/// </summary>
		protected const string __GenericError = "An error has occurred while calling the PWS Rest Web Services";

		internal PwsServiceException(Exception ex)
			: base(__GenericError, ex)
		{
		}

		internal PwsServiceException(String message)
			: base(message)
		{
		}
	}

	/// <summary>
	/// Represents a specific exception which has been thrown because the PWS Rest service has rejected the client's request.
	/// This is used as a base class for other exceptions.
	/// </summary>
	public class PwsServiceResponseException : PwsServiceException
	{
		internal PwsServiceResponseException(WebException we) : base(we)
		{
			if (we.Status == WebExceptionStatus.ProtocolError)
			{
				HttpWebResponse response = (HttpWebResponse)we.Response;

				try
				{
					using (Stream responseObjectStream = response.GetResponseStream())
					{
						if (response.ContentLength != 0)
						{
							// Extract the Fault object which the service will have returned.
							DataContractSerializer dcs = new DataContractSerializer(typeof(Fault_V1));
							Fault_V1 = dcs.ReadObject(responseObjectStream) as Fault_V1;
						}
					}
				}
				catch (Exception)
				{
					// Processing the Response stream has failed. The exception which came from the service operation
					// is in an unexpected form.
				}

				// Because the Message property is overridden, provide a default Fault_V1 object.
				if (Fault_V1 == null)
				{
					Fault_V1 = new Fault_V1()
					{
						Exception = "No Fault object was received from the service. " + __GenericError,
						HumanReadable = __GenericError,
						Reason = String.Empty
					};
				}
			}
		}

		/// <summary>
		/// The default implementation of Message for a PwsServiceResponseException (or anything derived from that)
		/// is to return the HumanReadable part of the Fault message which was returned by the service.
		/// </summary>
		public override string Message
		{
			get { return Fault_V1.HumanReadable; }
		}

		/// <summary>
		/// If the service returned a Fault_V1 object then it will be available from this property.
		/// </summary>
		public Fault_V1 Fault_V1 { get; private set; }
	}
	#endregion

	#region Service Exceptions
	/// <summary>
	/// The object being sent has undergone a version change since it was last retrieved.
	/// Therefore please re-retrieve the object and merge any changes before sending the object back again.
	/// </summary>
	/// <remarks>
	/// This is detected by HttpStatusCode 409:Conflict.
	/// </remarks>
	public class PwsServiceConflictException : PwsServiceResponseException
	{
		internal PwsServiceConflictException(WebException we)
			: base(we)
		{
		}
	}

	/// <summary>
	/// The user's credentials are invalid or have expired.
	/// </summary>
	/// <remarks>
	/// This is detected by HttpStatusCode 401:Unauthorized.
	/// </remarks>
	public class PwsServiceAuthenticationException : PwsServiceResponseException
	{
		internal PwsServiceAuthenticationException(WebException we)
			: base(we)
		{
		}
	}

	/// <summary>
	/// THe user's credentials are insufficient for the requested operation or object.
	/// </summary>
	/// <remarks>
	/// This is detected by HttpStatusCode 403:Forbidden.
	/// </remarks>
	public class PwsServicePermissionException : PwsServiceResponseException
	{
		internal PwsServicePermissionException(WebException we)
			: base(we)
		{
		}
	}

	/// <summary>
	/// The object being sent to the service has triggered a validation business rule to fail.
	/// </summary>
	/// <remarks>
	/// This is detected by HttpStatusCode 412:PreconditionFailed.
	/// </remarks>
	public class PwsServiceValidationException : PwsServiceResponseException
	{
		internal PwsServiceValidationException(WebException we)
			: base(we)
		{
		}
	}
	#endregion

	#region Logic Exceptions
	/// <summary>
	/// The relationship between some objects does not exist in this instance, caused by using IPwsObjectWrapper.Follow on an empty link.
	/// For example, attempting to follow the PrimarySupplier off a Token which is linked only to a customer and not a supplier.
	/// </summary>
	public class PwsServiceNullRelationshipException : PwsServiceException
	{
		internal PwsServiceNullRelationshipException(IPwsObject_V1 mainObject, Type subObjectType)
			: base(mainObject.GetType().Name + " " + mainObject.ToString() + " has no link to a " + subObjectType.Name)
		{
		}
	}
	#endregion

	/// <summary>
	/// This is a helper class for throwing the appropriate PwsServiceException based upon the cause of the base exception.
	/// </summary>
	internal static class ExceptionFactory
	{
		/// <summary>
		/// Throws an exception derived from PwsServiceException during a service operation.
		/// </summary>
		/// <param name="e"></param>
		public static void ThrowException(Exception e)
		{
			// Check for a WebException resulting from the service throwing an error back.
			WebException we = e as WebException;
			if (we != null && we.Status == WebExceptionStatus.ProtocolError)
			{
				// For a number of predetermined StatusCodes, there are explicit Exception types defined to assist in the client program flow.
				HttpWebResponse response = (HttpWebResponse)we.Response;
				switch (response.StatusCode)
				{
					case HttpStatusCode.Conflict: throw new PwsServiceConflictException(we);
					case HttpStatusCode.Unauthorized: throw new PwsServiceAuthenticationException(we);
					case HttpStatusCode.Forbidden: throw new PwsServicePermissionException(we);
					case HttpStatusCode.PreconditionFailed: throw new PwsServiceValidationException(we);

					// Not an expected response code, so throw a generic response exception.
					default:
						throw new PwsServiceResponseException(we);
				}
			}

			// Some other form of exception, which has not come from the service. This might be a network problem, for instance.
			throw new PwsServiceException(e);
		}
	}
}
